﻿using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using TianShu.TenantManagement.Dal.Abstracts.Entities;
using TianShu.TenantManagement.Dal.Abstracts.Etos;
using Volo.Abp.Threading;
using Volo.Abp;

namespace TianShu.TenantManagement.Dal.Manager
{
    public class TenantStore : ITenantStore, ITransientDependency
    {
        protected IRepository<TenantEntity, Guid> TenantRepository { get; }
        protected IRepository<TenantConnectionStringEntity, Guid> TenantConnectionStringRepository { get; }
        protected ICurrentTenant CurrentTenant { get; }
        protected IDistributedCache<TenantCacheItem> Cache { get; }
        protected IObjectMapper<TianShuTenantManagementDalModule> ObjectMapper { get; }

        public TenantStore(IRepository<TenantEntity, Guid> tenantRepository
                            , IRepository<TenantConnectionStringEntity, Guid> tenantConnectionStringRepository
                            , ICurrentTenant currentTenant
                            , IDistributedCache<TenantCacheItem> cache
                            , IObjectMapper<TianShuTenantManagementDalModule> cbjectMapper)
        {
            TenantRepository = tenantRepository;
            TenantConnectionStringRepository = tenantConnectionStringRepository;
            CurrentTenant = currentTenant;
            Cache = cache;
            ObjectMapper = cbjectMapper;
        }

        [Obsolete("Use FindAsync method.")]
        public virtual TenantConfiguration Find(string name)
        {
            return (GetCacheItem(null, name)).Value;
        }

        [Obsolete("Use FindAsync method.")]
        public virtual TenantConfiguration Find(Guid id)
        {
            return (GetCacheItem(id, null)).Value;
        }

        public virtual async Task<TenantConfiguration> FindAsync(string name)
        {
            return (await GetCacheItemAsync(null, name)).Value;
        }

        public virtual async Task<TenantConfiguration> FindAsync(Guid id)
        {
            return (await GetCacheItemAsync(id, null)).Value;
        }



        protected virtual async Task<TenantCacheItem> GetCacheItemAsync(Guid? id, string name)
        {
            var cacheKey = CalculateCacheKey(id, name);

            var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true);
            if (cacheItem != null)
            {
                return cacheItem;
            }

            if (id.HasValue)
            {
                using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
                {
                    var tenant = await TenantRepository.FindAsync(e => e.Id == id.Value);
                    if (tenant==null)
                    {
                        throw new ArgumentException($"租户id不存在或者被禁用，请联系管理员");
                    }
                    tenant.ConnectionStrings = (await TenantConnectionStringRepository.GetListAsync(e => e.TenantId == id.Value)).ToList();
                    return await SetCacheAsync(cacheKey, tenant);
                }
            }

            if (!name.IsNullOrWhiteSpace())
            {
                using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
                {
                    var tenant = await TenantRepository.FindAsync(e => e.Name == name);
                    tenant.ConnectionStrings = (await TenantConnectionStringRepository.GetListAsync(e => e.TenantId == tenant.Id)).ToList();
                    return await SetCacheAsync(cacheKey, tenant);
                }
            }

            throw new AbpException("Both id and name can't be invalid.");
        }
        protected virtual async Task<TenantCacheItem> SetCacheAsync(string cacheKey, [CanBeNull] TenantEntity tenant)
        {
            var tenantConfiguration = tenant != null ? ObjectMapper.Map<TenantEntity, TenantConfiguration>(tenant) : null;
            var cacheItem = new TenantCacheItem(tenantConfiguration);
            await Cache.SetAsync(cacheKey, cacheItem, considerUow: true);
            return cacheItem;
        }

        protected virtual string CalculateCacheKey(Guid? id, string name)
        {
            return TenantCacheItem.CalculateCacheKey(id, name);
        }

        [Obsolete("Use GetCacheItemAsync method.")]
        protected virtual TenantCacheItem GetCacheItem(Guid? id, string name)
        {
            var cacheKey = CalculateCacheKey(id, name);

            var cacheItem = Cache.Get(cacheKey, considerUow: true);
            if (cacheItem != null)
            {
                return cacheItem;
            }

            if (id.HasValue)
            {
                using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
                {
                    var tenant = AsyncHelper.RunSync<TenantEntity>(() => TenantRepository.FindAsync(e => e.Id == id.Value));
                    tenant.ConnectionStrings = AsyncHelper.RunSync(() => TenantConnectionStringRepository.GetListAsync(e => e.TenantId == tenant.Id)).ToList();
                    return SetCache(cacheKey, tenant);
                }
            }

            if (!name.IsNullOrWhiteSpace())
            {
                using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
                {
                    var tenant = AsyncHelper.RunSync<TenantEntity>(() => TenantRepository.FindAsync(e => e.Name == name));
                    tenant.ConnectionStrings = AsyncHelper.RunSync(() => TenantConnectionStringRepository.GetListAsync(e => e.TenantId == tenant.Id)).ToList();
                    return SetCache(cacheKey, tenant);
                }
            }

            throw new AbpException("Both id and name can't be invalid.");
        }

        [Obsolete("Use SetCacheAsync method.")]
        protected virtual TenantCacheItem SetCache(string cacheKey, [CanBeNull] TenantEntity tenant)
        {
            var tenantConfiguration = tenant != null ? ObjectMapper.Map<TenantEntity, TenantConfiguration>(tenant) : null;
            var cacheItem = new TenantCacheItem(tenantConfiguration);
            Cache.Set(cacheKey, cacheItem, considerUow: true);
            return cacheItem;
        }
    }
}
